<template>
  <ElForm ref="elForm" :model="data!" :rules="rules">
    <ElRow>
      <ElCol
        v-for="config of list"
        v-show="!(itemViewHideStatus[config.key] ?? false)"
        :key="config.key"
        v-bind="mergeLayout(config.layout)"
      >
        <FormItem
          :data="data!"
          :config="assertConfig(config)"
          @view-hide-change="onViewHideChange"
          @submit="emits('submit', $event)"
        >
          <template #default="scopeData">
            <slot v-bind="scopeData" :name="config.key"></slot>
          </template>
        </FormItem>
      </ElCol>
      <slot :mergeLayout="mergeLayout"></slot>
    </ElRow>
  </ElForm>
</template>
<script
  lang="ts"
  setup
  generic="PO extends Record<string, any>, SO extends Record<string, any>"
>
import type {
  FormItemConfig,
  FormMainInstance,
  FormItemLoadingChangeFunc,
  FormItemLoadingInfo,
  FormMainProps,
  FormItemSubmitType,
} from "./types";
import type {
  FormRules,
  FormInstance,
  ColProps,
  FormItemRule,
} from "element-plus";
import FormItem from "./FormItem.vue";
import { FORM_ITEM_CHANGE_LOADING } from "./utils";
const props = defineProps<FormMainProps<PO, SO>>();

const emits = defineEmits<{
  (e: "loadingChange" | "visibleChange", status: boolean): void;
  (e: "submit", type: FormItemSubmitType): void;
}>();

defineOptions({
  name: "FormMain",
  inheritAttrs: true,
});

const elForm = shallowRef<FormInstance>();

const loadingList = ref<FormItemLoadingInfo[]>([]);

const itemViewHideStatus = ref<{
  [key: string]: boolean;
}>({});

const loading = computed(() => {
  return !!loadingList.value.length;
});

/** 隐藏的表单keys */
const hideKeyList = computed(() => {
  return Object.keys(itemViewHideStatus.value).filter(
    (key) => itemViewHideStatus.value[key],
  );
});

/** 可见的表单 */
const visibleFormList = computed(() => {
  return props.list.filter((item) => !hideKeyList.value.includes(item.key));
});

/** 是否有可见的表单 */
const haveVisibleForm = computed(() => {
  return visibleFormList.value.length > 0;
});

const assertConfig = (config: FormItemConfig<PO, SO>) => {
  return config as FormItemConfig<Record<string, any>, Record<string, any>>;
};

const validate = () => {
  if (!elForm.value) return Promise.reject(new Error(`实例化未完成`));
  return new Promise<void>((resolve, reject) => {
    elForm.value?.validate((valid, error) => {
      if (valid) {
        resolve();
      } else {
        const errorClone = { ...(error || {}) };
        Object.keys(errorClone).forEach((key) => {
          if (hideKeyList.value.includes(key)) {
            console.log(`当前${key}被隐藏，移除校验`, errorClone[key]);
            delete errorClone[key];
            elForm.value?.clearValidate(key);
          }
        });
        if (Object.keys(errorClone).length === 0) {
          resolve();
        } else {
          reject(error);
        }
      }
    });
  });
};

const resetFields = () => {
  if (!elForm.value) return console.error(`实例化未完成`);
  return elForm.value.resetFields();
};

const clearValidate = (key?: string) => {
  if (!elForm.value) return console.error(`实例化未完成`);
  return elForm.value.clearValidate(key);
};

const expose: FormMainInstance = {
  validate,
  resetFields,
  clearValidate,
};

defineExpose(expose);

const list = computed(() => props.list);

const defaultRules = computed(() => {
  return list.value.reduce<Record<string, FormItemRule[]>>((prev, cur) => {
    if (cur.rules) {
      if (typeof cur.rules === "function") {
        prev[cur.key] = cur.rules({
          data: props.data,
          config: cur,
        });
      } else {
        prev[cur.key] = cur.rules;
      }
    }
    return prev;
  }, {});
});

const loadingRules = computed(() => {
  return loadingList.value.reduce<Record<string, FormItemRule[]>>(
    (prev, cur) => {
      prev[cur.key] = [
        {
          validator: () => [new Error(`${cur.label}${cur.message}`)],
        },
      ];
      return prev;
    },
    {},
  );
});

const rules = computed<FormRules>(() => {
  const defaultKeys = Object.keys(defaultRules.value);
  const loadingKeys = Object.keys(loadingRules.value);
  const keys = [...new Set(defaultKeys.concat(loadingKeys))];
  return keys.reduce<FormRules>((prev, cur) => {
    prev[cur] = [
      ...(defaultRules.value[cur] || []),
      ...(loadingRules.value[cur] || []),
    ];
    return prev;
  }, {});
});

const changeLoading: FormItemLoadingChangeFunc = ({ key, message }) => {
  if (!key) {
    return console.error(`更新状态未传入key, 此处忽略`);
  }
  const index = loadingList.value.findIndex((item) => item.key === key);
  if (index === -1) {
    if (message) {
      loadingList.value.push({
        key,
        message,
        label: props.list.find((item) => item.key === key)!.label,
      });
    }
  } else {
    if (message) {
      loadingList.value.splice(index, 1, {
        key,
        message,
        label: props.list.find((item) => item.key === key)!.label,
      });
    } else {
      loadingList.value.splice(index, 1);
    }
  }
};

provide<FormItemLoadingChangeFunc>(FORM_ITEM_CHANGE_LOADING, changeLoading);

const onViewHideChange = ({
  status,
  config: { key },
}: {
  config: FormItemConfig<Record<string, any>, Record<string, any>>;
  status: boolean;
}) => {
  const oldStatus = itemViewHideStatus.value[key] || false;
  if (oldStatus === status) return;
  itemViewHideStatus.value[key] = status;
};

const mergeLayout = (layout?: Partial<ColProps>) => {
  return {
    ...(props.layout || {}),
    ...(layout || {}),
  };
};

watch(
  list,
  () => {
    itemViewHideStatus.value = {};
  },
  {
    immediate: true,
  },
);

watch(loading, () => {
  emits("loadingChange", loading.value);
});

watch(
  haveVisibleForm,
  (n) => {
    emits("visibleChange", n);
  },
  {
    immediate: true,
  },
);
</script>
